#pragma once

using namespace System::Threading;
using namespace System::ComponentModel;

namespace ManagedWin32
{
	static_assert(Timeout::Infinite == INFINITE, "");

	public ref class Win32Thread : public IDisposable
	{
	private:

		ThreadStart^ m_threadStart;

		delegate int ThreadProc(IntPtr cookie);

		ThreadProc^ m_threadProc;
		
		HANDLE m_handle;

		DWORD m_threadId;

		int ThreadProcImpl(IntPtr cookie)
		{
			m_threadStart();
			return 0;
		}

	public:

		Win32Thread(ThreadStart^ threadStart)
			: m_threadStart(threadStart)
		{
			m_threadProc = gcnew ThreadProc(this, &Win32Thread::ThreadProcImpl);
			IntPtr functionPointer = Marshal::GetFunctionPointerForDelegate(m_threadProc);
			LPTHREAD_START_ROUTINE threadStartRoutine = static_cast<LPTHREAD_START_ROUTINE>(functionPointer.ToPointer());
			DWORD threadId;
			m_handle = CreateThread(NULL, 0, threadStartRoutine, NULL, 0, &threadId);
			if (m_handle == NULL) {
				throw gcnew Win32Exception(GetLastError());
			}
			m_threadId = threadId;
		}

		~Win32Thread()
		{
			this->!Win32Thread();
		}

		!Win32Thread()
		{
			if (m_handle != NULL) {
				CloseHandle(m_handle);
				m_handle = NULL;
			}
		}

		property int ThreadId 
		{
			int get() { return m_threadId; }
		}

		bool CancelSynchronousIO()
		{
			if (!CancelSynchronousIo(m_handle)) {
				DWORD errorCode = GetLastError();
				if (errorCode != ERROR_NOT_FOUND) {
					throw gcnew Win32Exception(errorCode);
				}
				return false;
			} else {
				return true;
			}
		}

		bool Join(int timeoutMillis)
		{
			DWORD result = WaitForSingleObject(m_handle, timeoutMillis);
			switch (result)
			{
			case WAIT_OBJECT_0:
				return true;
			case WAIT_TIMEOUT:
				return false;
			case WAIT_FAILED:
				throw gcnew Win32Exception(GetLastError());
			default:
				throw gcnew Win32Exception("WaitForSingleObject() returned unexpected result: " + result.ToString());
			}
		}
	};
}